from walle_event import build_time_tick_event, build_init_event
from walle_priority_queue import WallePriorityQueue


class WalleWorld:
    def __init__(self, name, environment):
        self.name = name
        self.event_queue = WallePriorityQueue()
        self.environment = environment
        self.event_log = []
        self.agents = []
        self.agents_map = {}
        self.curr_time = 0
        self.print_log = True
        self.save_log = False
        self.event_listeners = []
        self.event_sources = []

    def disable_log_print(self):
        self.print_log = False

    def add_world_observer(self, listener):
        self.event_listeners.append(listener)

    def add_event_source(self, source):
        self.event_sources.append(source)

    def __log_event(self, event):
        if self.print_log:
            print(str(event))
        if self.save_log:
            self.event_log.append(str(event))

    def __sort_agents(self):
        # for agent sequence sort
        def compAgent(a1, a2):
            if a1["seq"] > a2["seq"]:
                return 1
            elif a1["seq"] < a2["seq"]:
                return -1
            else:
                return 0

        self.agents.sort(compAgent)

    def __trigger_agent(self, agent, event):
        return agent.onEvent(self.get_curr_time(), self.environment, self.agents, event)

    def add_agent(self, agent, seq=0):
        inner_agent = {
            "agent_id": agent.agent_id,
            "seq": seq,
            "body": agent
        }
        self.agents.append(inner_agent)
        self.agents_map[agent.agent_id] = inner_agent

    def add_event(self, event, log_event=True):
        if log_event:
            self.__log_event(event)
        self.event_queue.add_task(event, event.trigger_time)

    def get_curr_time(self):
        copy_curr_time = self.curr_time
        return copy_curr_time

    def run(self, ticks):
        self.curr_time = 0

        self.__sort_agents()

        # send time tick 0
        print("created\tto trigger\t   type  \t  from  \t  to  \t    body")
        self.add_event(build_init_event())
        self.add_event(build_time_tick_event(0))

        while self.curr_time <= ticks:
            for listener in self.event_listeners:
                listener(self.get_curr_time(), self.environment, self.agents)
            for source in self.event_sources:
                for event in source(self.get_curr_time(), self.environment, self.agents):
                    self.add_event(event)
            while True:
                new_event = self.event_queue.pop_task()
                if new_event is None:
                    break
                elif new_event.trigger_time > self.curr_time:
                    # no remain event exists for this time tick
                    # push back
                    self.add_event(new_event, False)
                    break
                elif new_event.trigger_time == self.curr_time:
                    if new_event.target is None or new_event.target == "":
                        # for public event
                        for agent in self.agents:
                            output_events = self.__trigger_agent(agent["body"], new_event)
                            if output_events is not None:
                                for agent_event in output_events:
                                    if agent_event is not None:
                                        agent_event.create_time = self.curr_time
                                        agent_event.source = agent["agent_id"]
                                        self.add_event(agent_event)
                    else:
                        # for private event
                        agent = self.agents_map[new_event.target]
                        if agent is not None:
                            output_events = self.__trigger_agent(agent["body"], new_event)
                            if output_events is not None:
                                for agent_event in output_events:
                                    if agent_event is not None:
                                        agent_event.create_time = self.curr_time
                                        agent_event.source = agent["agent_id"]
                                        self.add_event(agent_event)
                else:
                    print("Get missed event at time " + new_event.trigger_time, new_event)

            self.curr_time = self.curr_time + 1
            self.add_event(build_time_tick_event(self.curr_time ))

    def dump_event_log(self, dir="../"):
        f = open(dir + "event_log_%s.log" % (self.name), 'a')
        for log in self.event_log:
            f.write(str(log) + "\n")
        f.flush()
        f.close()
